//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/tty/ccontrolledio.h>
#include "cscreen.h"
#include "cstatesaver.h"
#include <buola/tty/output.h>
#include <term.h>

namespace buola { namespace tty {

CControlledIO::CControlledIO(CScreen *pScreen)
    :   mScreen(pScreen)
    ,   mOn(false)
{
    if(!mScreen)
        mScreen=CScreen::GetDefault();
    CScreen::WatchWinSize(std::bind(&CControlledIO::OnWINCH,this));
}

CControlledIO::~CControlledIO()
{
}

void CControlledIO::Start()
{
    mScreen->PushState();

    mScreen->SetAutowrap(false);
    mScreen->SetEcho(false);
    mScreen->SetKeypad(true);
    mScreen->SetInputMode(tty::MODE_RAW);

    mWaitingInput=false;
    mWINCHSignalled=false;
    mOn=true;

    auto lSize=mScreen->GetWinSize();
    mHeight=lSize.first;
    mWidth=lSize.second;

    mCursor.Set(0,0);
    mBottom.Set(0,0);
}

void CControlledIO::End()
{
    mOn=false;
    mScreen->PopState();
}

CCursorPos CControlledIO::BreakString(const std::wstring &pString,std::vector<SBlock> &pBlocks,int pIS,const CCursorPos &pOS)
{
    int lIS=pIS;
    CCursorPos lOS=pOS;
    CCursorPos lOP=pOS;
    for(int lIP=pIS;lIP<pString.length();lIP++)
    {
        if(pString[lIP]==037) //used to mark non-printing characters
        {
            //emit partial line block
            if(lOP!=lOS)
            {
                pBlocks.push_back(SBlock(pString,lIS,lIP,lOS.c,lOP.c,false,false));
                lOS=lOP;
            }
            lIS=lIP+1;
            //search for control block ending
            for(++lIP;lIP<pString.length()&&pString[lIP]!=037;++lIP) {}
            //emit control block
            if(lIP<pString.length())
            {
                pBlocks.push_back(SBlock(pString,lIS,lIP,lOS.c,lOP.c,false,true));
                lIS=lIP+1;
            }
            continue;
        }
        
        int lW=wcwidth(pString[lIP]);
        
        if(lW<=0) continue;
        
        if(lOP.c+lW>mWidth) //emit current line
        {
            pBlocks.push_back(SBlock(pString,lIS,lIP,lOS.c,lOP.c,true,false));
            lIS=lIP;
            lOS.c=0;
            lOS.r++;
            lOP=lOS;
        }
        
        lOP.c+=lW;
    }
    
    //emit final line
    pBlocks.push_back(SBlock(pString,lIS,pString.length(),lOS.c,lOP.c,lOP.c==mWidth,false));
    if(lOP.c==mWidth)
    {
        lOP.c=0;
        lOP.r++;
    }
    
    return lOP;
}

void CControlledIO::OutputBlocks(std::vector<SBlock>::const_iterator pS,std::vector<SBlock>::const_iterator pE,
                                 int pFrom,const std::pair<int,int> &pSelection)
{
    int lSelB=-1,lSelE=-1;
    if(pSelection.first!=pSelection.second)
    {
        if(pSelection.second>pFrom)
        {
            lSelB=std::max(pSelection.first,pFrom);
            lSelE=pSelection.second;
        }
    }
    
    bool lSelMode=false;
    for(std::vector<SBlock>::const_iterator i=pS;i!=pE;++i)
    {
        if(i->mIE<=pFrom)
            continue;
        int lFrom=std::max(i->mIS,pFrom);

        if(lFrom<=lSelB&&i->mIE>lSelB)
        {
            mScreen->Output(i->mString->substr(lFrom,lSelB-lFrom));
            lFrom=lSelB;
            mScreen->Output(enter_reverse_mode);
            lSelMode=true;
        }
        
        if(lFrom<=lSelE&&i->mIE>lSelE)
        {
            mScreen->Output(i->mString->substr(lFrom,lSelE-lFrom));
            lFrom=lSelE;
            mScreen->Output(exit_standout_mode);
            lSelMode=false;
        }
        
        mScreen->Output(i->mString->substr(lFrom,i->mIE-lFrom));
        mCursor.c=i->mOE;
        if(i->mNL)
        {
            if(mCursor.c<mWidth)
                mScreen->ClearToEOL();
            NewLine();
        }
    }
    
    if(lSelMode)
        mScreen->Output(exit_standout_mode);
}

void CControlledIO::MoveCursor(const CCursorPos &pPos)
{
    mScreen->CarriageReturn();
    if(pPos.r<mCursor.r)
    {
        for(int i=pPos.r;i<mCursor.r;i++)
            mScreen->CursorUp();
    }
    else
    {
        for(int i=mCursor.r;i<pPos.r;i++)
            mScreen->CursorDown();
    }
    for(int i=0;i<pPos.c;i++)
        mScreen->CursorRight();
    mCursor=pPos;
}

void CControlledIO::SetCursor(bool pOn)
{
    mScreen->SetCursor(pOn);
}

void CControlledIO::ClearToEnd()
{
    if(mCursor.r<mBottom.r)
        mScreen->ClearToEOS();
    else
        mScreen->ClearToEOL();
    mBottom=mCursor;
}

void CControlledIO::ClearToEndOf(int pR)
{
    if(pR>mBottom.r) pR=mBottom.r;
    if(pR<mCursor.r) return;
    if(pR==mBottom.r)
        mScreen->ClearToEOS();
    else
    {
        mScreen->ClearToEOL();
        
        if(pR>mCursor.r)
        {
            mScreen->SaveCursor();
            for(int i=0;i<pR-mCursor.r;i++)
            {
                NewLine();
                mScreen->ClearToEOL();
            }
            mScreen->RestoreCursor();
        }
    }
    mBottom=mCursor;
}

void CControlledIO::ClearToEOL()
{
    mScreen->ClearToEOL();
}

void CControlledIO::NewLine()
{
    mScreen->Output(L"\n");
    mCursor.r++;
    mCursor.c=0;
    
    if(mCursor>mBottom) mBottom=mCursor;
}

void CControlledIO::Output(const std::wstring &pStr,int /*pLen*/)
{
    mScreen->Output(pStr);
}

wchar_t CControlledIO::GetWChar()
{
    mWaitingInput=true;
    if(mWINCHSignalled)
        OnWINCH();

    wchar_t lCh;
    try
    {
        lCh=mScreen->GetWChar();
    }
    catch(...)
    {
        lCh=0;
    }    
    
    mWaitingInput=false;
    return lCh;
}

char CControlledIO::GetChar()
{
    mWaitingInput=true;
    if(mWINCHSignalled)
        OnWINCH();

    char lCh;
    try
    {
        lCh=mScreen->GetChar();
    }
    catch(...)
    {
        lCh=0;
    }    
    
    mWaitingInput=false;
    return lCh;
}

void CControlledIO::OnWINCH()
{
    if(mOn)
    {
        if(mWaitingInput)
        {
            auto lSize=mScreen->GetWinSize();
            mHeight=lSize.first;
            mWidth=lSize.second;
    
            sWinSizeChanged();
        }
        else
            mWINCHSignalled=true;
    }
}

/*namespace tty*/ } /*namespace buola*/ }
